package com.sina.mysql.utils;

import org.hibernate.HibernateException;
import org.hibernate.annotations.Type;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.spi.*;
import org.hibernate.usertype.DynamicParameterizedType;
import org.hibernate.usertype.EnhancedUserType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import static com.sina.mysql.utils.Assert.assertNotNull;

/**
 * @ClassName CodeEnumUserType
 * @Description:
 * @Author 段浩杰
 * @Date 2018/8/22 17:54
 * @Version 1.0
 */
public class CodeEnumUserType<T extends CodeEnum> implements EnhancedUserType, DynamicParameterizedType, org.hibernate.type.spi.TypeConfigurationAware {

    private static final Logger LOG = LoggerFactory.getLogger( CodeEnumUserType.class );

    private Class<T> codeEnumType;

    private TypeConfiguration typeConfiguration;

    private Map<String, T> codeMap;

    private Map<String, T> nameMap;

    private final int[] SQL_TYPE = new int[]{Types.INTEGER};

    @Override
    public TypeConfiguration getTypeConfiguration() {
        return typeConfiguration;
    }

    @Override
    public void setTypeConfiguration(TypeConfiguration typeConfiguration) {
        this.typeConfiguration = typeConfiguration;
    }

    @SuppressWarnings("unchecked")
    @Override
    public void setParameterValues(Properties parameters) {
        //仅支持 注解配置
        final ParameterType reader = (ParameterType) parameters.get( PARAMETER_TYPE );
        assertParameterType( reader );

        codeEnumType = reader.getReturnedClass().asSubclass( CodeEnum.class );
        if (!codeEnumType.isEnum()) {
            throw new IllegalStateException( String.format( "class[%s] isn't Enum", codeEnumType ) );
        }

        T[] codeEnums = codeEnumType.getEnumConstants();
        final int initialCapacity = (int) (codeEnums.length / 0.7f);
        Map<String, T> codeMap = new HashMap<>( initialCapacity );
        Map<String, T> nameMap = new HashMap<>( initialCapacity );
        String codeKey;
        for (T e : codeEnums) {
            codeKey = String.valueOf( e.code() );
            if (codeMap.containsKey( codeKey )) {
                throw new RuntimeException( String.format( "code[%s] duplicate", codeKey ) );
            }
            codeMap.put( codeKey, e );
            nameMap.put( e.name(), e );
        }
        this.codeMap = Collections.unmodifiableMap( codeMap );
        this.nameMap = Collections.unmodifiableMap( nameMap );
        if (typeConfiguration == null) {
            LOG.info( "typeConfiguration not config" );
        }
    }

    private void assertParameterType(ParameterType reader) throws IllegalStateException, IllegalArgumentException {
        if (reader == null) {
            throw new IllegalStateException( String.format( "%s 只支持注解配置", getClass().getName() ) );
        }
        if (!CodeEnum.class.isAssignableFrom( reader.getReturnedClass() )) {
            throw new IllegalArgumentException( String.format( "被 %s 标注的 %s 不是 %s 的子类"
                    , Type.class.getName()
                    , reader.getReturnedClass().getName()
                    , CodeEnum.class.getName() ) );
        }
    }


    @Override
    public int[] sqlTypes() {
        return SQL_TYPE;
    }

    @SuppressWarnings("unchecked")
    @Override
    public Class<? extends Enum> returnedClass() {
        return (Class<? extends Enum>) codeEnumType;
    }

    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        return x == y;
    }

    @Override
    public int hashCode(Object x) throws HibernateException {
        return x == null ? 0 : x.hashCode();
    }

    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
        assertNotNull( codeEnumType, "type not config" );
        final String value = rs.getString( names[0] );
        return codeMap.get( value );
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
        assertNotNull( codeEnumType, "type not config" );
        if (value == null) {
            st.setNull( index, Types.INTEGER );
        } else {
            st.setInt( index, ((CodeEnum) value).code() );
        }
    }

    @Override
    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }

    @Override
    public boolean isMutable() {
        return false;
    }

    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) value;
    }

    @Override
    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return cached;
    }

    @Override
    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }

    @Override
    public String objectToSQLString(Object value) {
        assertNotNull( codeEnumType, "type not config" );
        return String.valueOf( ((CodeEnum) value).code() );
    }

    @Override
    public String toXMLString(Object value) {
        return String.valueOf( ((CodeEnum) value).name() );
    }

    @Override
    public Object fromXMLString(String xmlValue) {
        assertNotNull( codeEnumType, "type not config" );
        return nameMap.get( xmlValue );
    }
}
